package com.example.controller;

import com.example.annotation.RateLimit;
import com.example.config.RateLimitService;
import com.example.exception.BusinessException;
import com.example.model.User;
import com.example.service.UserService;
import com.example.util.EnhancedJwtTokenUtil;
import com.example.util.SecurityUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/api/secure-auth")
@Tag(name = "安全认证管理", description = "增强安全的用户认证接口")
@RequiredArgsConstructor
public class SecureAuthController {

    private final AuthenticationManager authenticationManager;
    private final UserService userService;
    private final EnhancedJwtTokenUtil jwtTokenUtil;
    private final PasswordEncoder passwordEncoder;
    private final SecurityUtils securityUtils;
    private final RateLimitService rateLimitService;

    @PostMapping("/login")
    @Operation(summary = "安全登录")
    @RateLimit(key = "login", type = RateLimit.LimitType.IP, limit = 5, window = 300, message = "登录尝试过于频繁，请5分钟后再试")
    public ResponseEntity<Map<String, Object>> login(@Valid @RequestBody LoginRequest loginRequest, HttpServletRequest request) {
        try {
            validateLoginInput(loginRequest);
            
            String clientIp = getClientIp(request);
            if (!rateLimitService.checkLoginLimit(clientIp)) {
                throw new BusinessException(com.example.common.ResultCode.TOO_MANY_REQUESTS, "登录尝试过于频繁，请稍后再试");
            }

            String username = loginRequest.getUsername();
            String password = loginRequest.getPassword();

            Authentication authentication = authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(username, password)
            );

            User user = userService.findByUsername(username);
            if (user == null) {
                throw new BusinessException(com.example.common.ResultCode.USER_NOT_FOUND);
            }

            Map<String, Object> claims = new HashMap<>();
            claims.put("userId", user.getId());
            claims.put("email", user.getEmail());
            
            String accessToken = jwtTokenUtil.generateAccessToken(username, "USER", claims);
            String refreshToken = jwtTokenUtil.generateRefreshToken(username, "USER");

            log.info("用户登录成功: username={}, ip={}", username, clientIp);

            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("accessToken", accessToken);
            response.put("refreshToken", refreshToken);
            response.put("tokenType", "Bearer");
            response.put("expiresIn", 86400);
            response.put("user", Map.of(
                "id", user.getId(),
                "username", user.getUsername(),
                "email", user.getEmail()
            ));

            return ResponseEntity.ok(response);
        } catch (BadCredentialsException e) {
            log.warn("登录失败: username={}, ip={}", loginRequest.getUsername(), getClientIp(request));
            throw new BusinessException(com.example.common.ResultCode.PASSWORD_ERROR);
        }
    }

    @PostMapping("/register")
    @Operation(summary = "安全注册")
    @RateLimit(key = "register", type = RateLimit.LimitType.IP, limit = 3, window = 3600, message = "注册过于频繁，请1小时后再试")
    public ResponseEntity<Map<String, Object>> register(@Valid @RequestBody RegisterRequest registerRequest, HttpServletRequest request) {
        try {
            validateRegisterInput(registerRequest);
            
            if (userService.existsByUsername(registerRequest.getUsername())) {
                throw new BusinessException(com.example.common.ResultCode.USER_ALREADY_EXISTS, "用户名已存在");
            }

            if (userService.existsByEmail(registerRequest.getEmail())) {
                throw new BusinessException(com.example.common.ResultCode.DATA_ALREADY_EXISTS, "邮箱已被注册");
            }

            User user = new User();
            user.setUsername(registerRequest.getUsername());
            user.setEmail(registerRequest.getEmail());
            user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));

            User savedUser = userService.save(user);

            log.info("用户注册成功: username={}, email={}, ip={}", 
                    savedUser.getUsername(), savedUser.getEmail(), getClientIp(request));

            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("message", "注册成功");
            response.put("userId", savedUser.getId());

            return ResponseEntity.ok(response);
        } catch (Exception e) {
            if (e instanceof BusinessException) {
                throw e;
            }
            throw new BusinessException(com.example.common.ResultCode.BUSINESS_ERROR, "注册失败，请稍后再试");
        }
    }

    @PostMapping("/refresh")
    @Operation(summary = "刷新令牌")
    @RateLimit(key = "refresh", type = RateLimit.LimitType.USER, limit = 10, window = 3600)
    public ResponseEntity<Map<String, Object>> refreshToken(@Valid @RequestBody RefreshTokenRequest request) {
        try {
            String newAccessToken = jwtTokenUtil.refreshAccessToken(request.getRefreshToken());
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("accessToken", newAccessToken);
            response.put("tokenType", "Bearer");
            response.put("expiresIn", 86400);

            return ResponseEntity.ok(response);
        } catch (Exception e) {
            log.error("刷新令牌失败: {}", e.getMessage());
            throw new BusinessException(com.example.common.ResultCode.TOKEN_INVALID, "刷新令牌失败");
        }
    }

    @PostMapping("/logout")
    @Operation(summary = "安全登出")
    public ResponseEntity<Map<String, Object>> logout(HttpServletRequest request) {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.isAuthenticated()) {
                String username = authentication.getName();
                jwtTokenUtil.revokeAllUserTokens(username);
                log.info("用户登出: username={}, ip={}", username, getClientIp(request));
            }
            
            SecurityContextHolder.clearContext();
            
            Map<String, Object> response = new HashMap<>();
            response.put("success", true);
            response.put("message", "登出成功");
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            log.error("登出异常: {}", e.getMessage(), e);
            throw new BusinessException(com.example.common.ResultCode.LOGOUT_FAILED, "登出失败");
        }
    }

    private void validateLoginInput(LoginRequest request) {
        if (!StringUtils.hasText(request.getUsername()) || !StringUtils.hasText(request.getPassword())) {
            throw new BusinessException(com.example.common.ResultCode.BAD_REQUEST, "用户名和密码不能为空");
        }

        if (securityUtils.containsSqlInjection(request.getUsername()) ||
            securityUtils.containsXss(request.getUsername())) {
            throw new BusinessException(com.example.common.ResultCode.BAD_REQUEST, "用户名包含非法字符");
        }
    }

    private void validateRegisterInput(RegisterRequest request) {
        if (!securityUtils.isSafeUsername(request.getUsername())) {
            throw new BusinessException(com.example.common.ResultCode.BAD_REQUEST, "用户名格式不正确");
        }

        if (!securityUtils.isValidEmail(request.getEmail())) {
            throw new BusinessException(com.example.common.ResultCode.BAD_REQUEST, "邮箱格式不正确");
        }

        if (!securityUtils.isStrongPassword(request.getPassword())) {
            throw new BusinessException(com.example.common.ResultCode.BAD_REQUEST, "密码强度不够");
        }
    }

    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ip != null && ip.contains(",")) {
            ip = ip.split(",")[0].trim();
        }
        return ip != null ? ip : "unknown";
    }

    @Data
    public static class LoginRequest {
        @NotBlank(message = "用户名不能为空")
        @Size(min = 3, max = 20, message = "用户名长度必须在3-20位之间")
        private String username;
        
        @NotBlank(message = "密码不能为空")
        @Size(min = 6, max = 50, message = "密码长度必须在6-50位之间")
        private String password;
    }

    @Data
    public static class RegisterRequest {
        @NotBlank(message = "用户名不能为空")
        @Size(min = 3, max = 20, message = "用户名长度必须在3-20位之间")
        private String username;
        
        @NotBlank(message = "邮箱不能为空")
        @Email(message = "邮箱格式不正确")
        private String email;
        
        @NotBlank(message = "密码不能为空")
        @Size(min = 8, max = 50, message = "密码长度必须在8-50位之间")
        private String password;
    }

    @Data
    public static class RefreshTokenRequest {
        @NotBlank(message = "刷新令牌不能为空")
        private String refreshToken;
    }
}
